package com.test.designPattern.behaviorType.interpreterPattern;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * 用栈实现逆波兰计算器
 *  主要步骤：
 *   一，先把中缀表达式转为后缀表达式
 *      1.1 初始化两个栈，运算符栈s1,存储中间结果栈s2
 *      1.2 从左至右扫描中缀表达式
 *      1.3 遇到操作数时把其压入s2
 *      1.4 遇到运算符时，用其与s1栈顶运算符比较优先级
 *           1.4.1 如果s1为空，或栈顶运算符为左括号“（”,则直接将此运算符入s1.
 *           1.4.2 如果s1不为空，栈顶运算符也不为左括号“（”，则与s1栈顶运算符比较优先级，若比s1栈顶优先级高，也将其压入s1.
 *           1.4.3 再否则，如果不比s1栈顶优先级高，就把s1栈顶运算符弹出并压入s2，然后再重复1.4与s1新栈顶比较
 *      1.5 遇到括号时：
 *           1.5.1 如果是“（”,则直接压入s1
 *           1.5.2 如果是“）”,则依次弹出s1栈顶运算符压入s2，直到遇到“（"为止，此时将这对括号丢弃
 *      1.6 重复步骤1.2--1.5直至到表达式最右边
 *      1.7 最后将s1剩余运算符依次弹出压入s2
 *      1.8 依次弹出s2中元素，其逆序就是此中缀表达式对应的后缀表达式
 *   二，按照逆波兰表单式的运算规则计算结果，以（3*4）*5-6为例，其后缀表达式为：3 4 + 5 * 6 -，计算步骤如下：
 *      2.1 从左至右扫描，将3和4压入栈s3
 *      2.2 遇到“+”，弹出3和4，计算3+4得7，将7再入栈s3
 *      2.3 继续扫描，将5入栈
 *      2.4 遇到“*”，弹出5和7,计算7*5，得35，将35入栈
 *      2.5 继续扫描，将6入栈
 *      2.6 最后是“-"，计算35-6，得29,这就是最终结果
 * @author wangjie
 * @version V1.0
 * @date 2020/3/1
 */
public class ReversePolishCalculator {


    /**
     * 判断是否是数字
     */
    private static final Pattern isNumber = Pattern.compile("^[-\\+]?[.\\d]*$");

    /**
     *
     * @param str
     * @return
     */
    public static Double calculator(String str){

        Double doub = 0.0D;
        try {
            //1,校验参数
            check(str);
            //2,转为逆波兰表达式
            List<String> list = toReversePolish(str);
            //3,计算结果
            doub = doCalc(list);
        }catch(Exception e){
            e.printStackTrace();
        }
        return doub;
    }

    /**
     * 计算结果
     * @param list
     * @return
     */
    private static Double doCalc(List<String> list) {
        Double d = 0.0D;

        if (list.size() == 1) {
            //如果只剩一个数字
            System.out.println(list);
            d = Double.valueOf((String)list.get(0));
            return d;
        } else {
            ArrayList<String> list1 = new ArrayList();

            for(int i = 0; i < list.size(); ++i) {
                list1.add((String)list.get(i));
                if (isSymbol((String)list.get(i))) {
                    Double d1 = doTheMath((String)list.get(i - 2), (String)list.get(i - 1), (String)list.get(i));
                    list1.remove(i);
                    list1.remove(i - 1);
                    list1.set(i - 2, "" + d1);
                    list1.addAll(list.subList(i + 1, list.size()));
                    break;
                }
            }

            doCalc(list1);
            return d;
        }
    }

    /**
     * 根据字符集判断运算符，进而计算
     * @param s1
     * @param s2
     * @param symbol
     * @return
     */
    private static Double doTheMath(String s1, String s2, String symbol) {
        Double result= null;

        Interpreter expressionObject = OperatorUtil.getExpressionObject(s1, s2, symbol);
        if (null != expressionObject){
            result = expressionObject.interpret();
        }

        return result;
    }
    /**
     * 参数校验
     * @param str
     */
    private static void check(String str){

        if(null != str && !"".equals(str.trim())){
            new RuntimeException("data is empty");
        }
        if(isNotNumber(String.valueOf(str.charAt(0)))){
            throw new RuntimeException("data illeagle,start not with a number");
        }
    }

    /**
     * 判断字符串是否是数字
     * @param str
     * @return
     */
    private static boolean isNotNumber(String str) {
        return !isNumber.matcher(str).matches();
    }

    /**
     * 中缀表达式转为后缀表达式
     * @param str
     * @return
     */
    private static List<String> toReversePolish(String str){

        List<String> data = Collections.synchronizedList(new ArrayList());
        Stack<String> stack = new Stack();
        str = replaceAllBlank(str);
        int start = 0;
        //遍历中缀表达式字符串
        for(int i = 0; i < str.length(); ++i) {
            String each;
            //如果不是运算符
            if (!isSymbol(String.valueOf(str.charAt(i)))) {
                //如果是最后一个字符或下个字符是运算符
                if (i == str.length() - 1 || isSymbol(String.valueOf(str.charAt(i + 1)))) {
                    each = start == 0 ? str.substring(start, i + 1) : str.substring(start + 1, i + 1);
                    if (isNotNumber(each)) {
                        throw new RuntimeException("data not match number");
                    }

                    data.add(each);
                }
            } else {
                each = String.valueOf(str.charAt(i));
                //如果是运算符“+-*/”或“（”
                if (!stack.isEmpty() && !"(".equals(each) && (calcLevel(each) <= calcLevel((String)stack.peek()) || calcLevel(each) >= 2147483647)) {
                    if (!stack.isEmpty() && calcLevel(each) <= calcLevel((String)stack.peek())) {
                        while(!stack.isEmpty() && calcLevel(each) <= calcLevel((String)stack.peek()) && calcLevel((String)stack.peek()) != 2147483647) {
                            data.add((String)stack.pop());
                        }

                        stack.push(each);
                    } else if (")".equals(each)) {
                        while(!stack.isEmpty() && 2147483647 >= calcLevel((String)stack.peek())) {
                            if (2147483647 == calcLevel((String)stack.peek())) {
                                stack.pop();
                                break;
                            }

                            data.add((String)stack.pop());
                        }
                    }
                } else {
                    stack.push(each);
                }

                start = i;
            }
        }

        Collections.reverse(stack);
        data.addAll(new ArrayList(stack));
        System.out.println(data);
        return data;

    }

    /**
     * 判断运算符优先级
     * @param s
     * @return
     */
    private static int calcLevel(String s) {
        if (!"+".equals(s) && !"-".equals(s)) {
            return !"*".equals(s) && !"/".equals(s) ? 2147483647 : 2;
        } else {
            return 1;
        }
    }

    /**
     * 判断是否运算符
     * @param s
     * @return
     */
    private static boolean isSymbol(String s) {
        return s.matches("\\+|-|\\*|/|\\(|\\)");
    }

    /**
     * 去除所有空格
     * @param s
     * @return
     */
    private static String replaceAllBlank(String s) {
        return s.replaceAll("\\s+", "");
    }

    public static void main(String[] args) {
        //定义一个中缀表达式字符串
        String str = "22.9 + (2+9.8)*4/5 +8-9";
        calculator(str);
    }


}

